﻿using System;
using System.IO;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.Reflection;
using System.Reflection.Emit;

namespace Dapper_Core.Basic
{
    /// <summary>
    /// 采用Emit方式克隆对象
    /// 用法 object obj=DataCloner.Clone(object);
    /// </summary>
    public static class DataCloner
    {
        delegate object ObjectCloneMethod(object obj);
        static readonly Hashtable MethodList = new Hashtable();

        public static T GenericClone<T>(T obj)
        {
            if (obj is ValueType || obj == null)
                return obj;

            ObjectCloneMethod cloneMethod = GetColoneMethod(obj.GetType());
            object result = cloneMethod(obj);
            return (T)result;
        }

        public static object Clone(object obj)
        {
            if (obj is ValueType || obj == null)
                return obj;

            return CloneObject(obj);
        }

        static object CloneObject(object obj)
        {
            using (MemoryStream memStream = new MemoryStream())
            {
                BinaryFormatter binaryFormatter = new BinaryFormatter(null, new StreamingContext(StreamingContextStates.Clone));
                binaryFormatter.Serialize(memStream, obj);
                memStream.Seek(0, SeekOrigin.Begin);
                return binaryFormatter.Deserialize(memStream);
            }
        }

        static ObjectCloneMethod GetColoneMethod(Type cloneType)
        {
            lock (MethodList.SyncRoot)
            {
                if (MethodList.ContainsKey(cloneType.GUID))
                    return (ObjectCloneMethod)MethodList[cloneType.GUID];

                Type[] methodArgs = { typeof(object) };
                DynamicMethod dynamicMethod = new DynamicMethod(string.Empty, typeof(object), methodArgs, cloneType);

                ILGenerator il = dynamicMethod.GetILGenerator();

                ConstructorInfo createInfo = cloneType.GetConstructor(new Type[0]);

                il.DeclareLocal(cloneType);
                il.Emit(OpCodes.Newobj, createInfo);
                il.Emit(OpCodes.Stloc_0);
                PropertyInfo[] Properties = cloneType.GetProperties(BindingFlags.Public | BindingFlags.Instance);
                foreach (PropertyInfo prop in Properties)
                {
                    if (!(prop.CanWrite && prop.CanRead))
                        continue;
                    il.Emit(OpCodes.Ldloc_0);
                    il.Emit(OpCodes.Ldarg_0);
                    il.Emit(OpCodes.Callvirt, prop.GetGetMethod());
                    il.Emit(OpCodes.Callvirt, prop.GetSetMethod());
                }

                il.Emit(OpCodes.Ldloc_0);
                il.Emit(OpCodes.Ret);
                ObjectCloneMethod result = null;
                try
                {
                    result = (ObjectCloneMethod)dynamicMethod.CreateDelegate(typeof(ObjectCloneMethod));
                }
                catch (Exception ex)
                {
                    throw ex;
                }
                MethodList.Add(cloneType.GUID, result);

                return result;
            }
        }
    }
}
